home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.0.14 / components / nsLoginManagerPrompter.js < prev    next >
Encoding:
Text File  |  2009-09-02  |  39.7 KB  |  1,117 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *  Ehsan Akhgari <ehsan.akhgari@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41. const Cr = Components.results;
  42.  
  43. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  44.  
  45. /*
  46.  * LoginManagerPromptFactory
  47.  *
  48.  * Implements nsIPromptFactory
  49.  *
  50.  * Invoked by NS_NewAuthPrompter2()
  51.  * [embedding/components/windowwatcher/src/nsPrompt.cpp]
  52.  */
  53. function LoginManagerPromptFactory() {}
  54.  
  55. LoginManagerPromptFactory.prototype = {
  56.  
  57.     classDescription : "LoginManagerPromptFactory",
  58.     contractID : "@mozilla.org/passwordmanager/authpromptfactory;1",
  59.     classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
  60.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
  61.  
  62.     getPrompt : function (aWindow, aIID) {
  63.         var prompt = new LoginManagerPrompter().QueryInterface(aIID);
  64.         prompt.init(aWindow);
  65.         return prompt;
  66.     }
  67. }; // end of LoginManagerPromptFactory implementation
  68.  
  69.  
  70.  
  71.  
  72. /* ==================== LoginManagerPrompter ==================== */
  73.  
  74.  
  75.  
  76.  
  77. /*
  78.  * LoginManagerPrompter
  79.  *
  80.  * Implements interfaces for prompting the user to enter/save/change auth info.
  81.  *
  82.  * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
  83.  *
  84.  * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
  85.  * (eg HTTP Authenticate, FTP login).
  86.  *
  87.  * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
  88.  * found in HTML forms.
  89.  */
  90. function LoginManagerPrompter() {}
  91.  
  92. LoginManagerPrompter.prototype = {
  93.  
  94.     classDescription : "LoginManagerPrompter",
  95.     contractID : "@mozilla.org/login-manager/prompter;1",
  96.     classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
  97.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
  98.                                             Ci.nsIAuthPrompt2,
  99.                                             Ci.nsILoginManagerPrompter]),
  100.  
  101.     _window        : null,
  102.     _debug         : false, // mirrors signon.debug
  103.  
  104.     __pwmgr : null, // Password Manager service
  105.     get _pwmgr() {
  106.         if (!this.__pwmgr)
  107.             this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
  108.                            getService(Ci.nsILoginManager);
  109.         return this.__pwmgr;
  110.     },
  111.  
  112.     __logService : null, // Console logging service, used for debugging.
  113.     get _logService() {
  114.         if (!this.__logService)
  115.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  116.                                 getService(Ci.nsIConsoleService);
  117.         return this.__logService;
  118.     },
  119.  
  120.     __promptService : null, // Prompt service for user interaction
  121.     get _promptService() {
  122.         if (!this.__promptService)
  123.             this.__promptService =
  124.                 Cc["@mozilla.org/embedcomp/prompt-service;1"].
  125.                 getService(Ci.nsIPromptService2);
  126.         return this.__promptService;
  127.     },
  128.  
  129.  
  130.     __strBundle : null, // String bundle for L10N
  131.     get _strBundle() {
  132.         if (!this.__strBundle) {
  133.             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
  134.                              getService(Ci.nsIStringBundleService);
  135.             this.__strBundle = bunService.createBundle(
  136.                         "chrome://passwordmgr/locale/passwordmgr.properties");
  137.             if (!this.__strBundle)
  138.                 throw "String bundle for Login Manager not present!";
  139.         }
  140.  
  141.         return this.__strBundle;
  142.     },
  143.  
  144.  
  145.     __brandBundle : null, // String bundle for L10N
  146.     get _brandBundle() {
  147.         if (!this.__brandBundle) {
  148.             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
  149.                              getService(Ci.nsIStringBundleService);
  150.             this.__brandBundle = bunService.createBundle(
  151.                         "chrome://branding/locale/brand.properties");
  152.             if (!this.__brandBundle)
  153.                 throw "Branding string bundle not present!";
  154.         }
  155.  
  156.         return this.__brandBundle;
  157.     },
  158.  
  159.  
  160.     __ioService: null, // IO service for string -> nsIURI conversion
  161.     get _ioService() {
  162.         if (!this.__ioService)
  163.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  164.                                getService(Ci.nsIIOService);
  165.         return this.__ioService;
  166.     },
  167.  
  168.  
  169.     /*
  170.      * log
  171.      *
  172.      * Internal function for logging debug messages to the Error Console window.
  173.      */
  174.     log : function (message) {
  175.         if (!this._debug)
  176.             return;
  177.  
  178.         dump("Pwmgr Prompter: " + message + "\n");
  179.         this._logService.logStringMessage("Pwmgr Prompter: " + message);
  180.     },
  181.  
  182.  
  183.  
  184.  
  185.     /* ---------- nsIAuthPrompt prompts ---------- */
  186.  
  187.  
  188.     /*
  189.      * prompt
  190.      *
  191.      * Wrapper around the prompt service prompt. Saving random fields here
  192.      * doesn't really make sense and therefore isn't implemented.
  193.      */
  194.     prompt : function (aDialogTitle, aText, aPasswordRealm,
  195.                        aSavePassword, aDefaultText, aResult) {
  196.         if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
  197.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  198.  
  199.         this.log("===== prompt() called =====");
  200.  
  201.         if (aDefaultText) {
  202.             aResult.value = aDefaultText;
  203.         }
  204.  
  205.         return this._promptService.prompt(this._window,
  206.                aDialogTitle, aText, aResult, null, {});
  207.     },
  208.  
  209.  
  210.     /*
  211.      * promptUsernameAndPassword
  212.      *
  213.      * Looks up a username and password in the database. Will prompt the user
  214.      * with a dialog, even if a username and password are found.
  215.      */
  216.     promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm,
  217.                                          aSavePassword, aUsername, aPassword) {
  218.         this.log("===== promptUsernameAndPassword() called =====");
  219.  
  220.         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
  221.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  222.  
  223.         var selectedLogin = null;
  224.         var checkBox = { value : false };
  225.         var checkBoxLabel = null;
  226.         var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);
  227.  
  228.         // If hostname is null, we can't save this login.
  229.         if (hostname) {
  230.             var canRememberLogin = (aSavePassword ==
  231.                                     Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
  232.                                    this._pwmgr.getLoginSavingEnabled(hostname);
  233.  
  234.             // if checkBoxLabel is null, the checkbox won't be shown at all.
  235.             if (canRememberLogin)
  236.                 checkBoxLabel = this._getLocalizedString("rememberPassword");
  237.  
  238.             // Look for existing logins.
  239.             var foundLogins = this._pwmgr.findLogins({}, hostname, null,
  240.                                                      realm);
  241.  
  242.             // XXX Like the original code, we can't deal with multiple
  243.             // account selection. (bug 227632)
  244.             if (foundLogins.length > 0) {
  245.                 selectedLogin = foundLogins[0];
  246.  
  247.                 // If the caller provided a username, try to use it. If they
  248.                 // provided only a password, this will try to find a password-only
  249.                 // login (or return null if none exists).
  250.                 if (aUsername.value)
  251.                     selectedLogin = this._repickSelectedLogin(foundLogins,
  252.                                                               aUsername.value);
  253.  
  254.                 if (selectedLogin) {
  255.                     checkBox.value = true;
  256.                     aUsername.value = selectedLogin.username;
  257.                     // If the caller provided a password, prefer it.
  258.                     if (!aPassword.value)
  259.                         aPassword.value = selectedLogin.password;
  260.                 }
  261.             }
  262.         }
  263.  
  264.         var ok = this._promptService.promptUsernameAndPassword(this._window,
  265.                     aDialogTitle, aText, aUsername, aPassword,
  266.                     checkBoxLabel, checkBox);
  267.  
  268.         if (!ok || !checkBox.value || !hostname)
  269.             return ok;
  270.  
  271.         var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  272.                        createInstance(Ci.nsILoginInfo);
  273.         newLogin.init(hostname, null, realm, aUsername.value, aPassword.value,
  274.                       "", "");
  275.  
  276.         // XXX We can't prompt with multiple logins yet (bug 227632), so
  277.         // the entered login might correspond to an existing login
  278.         // other than the one we originally selected.
  279.         selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);
  280.  
  281.         // If we didn't find an existing login, or if the username
  282.         // changed, save as a new login.
  283.         if (!selectedLogin) {
  284.             // add as new
  285.             this.log("New login seen for " + realm);
  286.             this._pwmgr.addLogin(newLogin);
  287.         } else if (aPassword.value != selectedLogin.password) {
  288.             // update password
  289.             this.log("Updating password for  " + realm);
  290.             this._pwmgr.modifyLogin(selectedLogin, newLogin);
  291.         } else {
  292.             this.log("Login unchanged, no further action needed.");
  293.         }
  294.  
  295.         return ok;
  296.     },
  297.  
  298.  
  299.     /*
  300.      * promptPassword
  301.      *
  302.      * If a password is found in the database for the password realm, it is
  303.      * returned straight away without displaying a dialog.
  304.      *
  305.      * If a password is not found in the database, the user will be prompted
  306.      * with a dialog with a text field and ok/cancel buttons. If the user
  307.      * allows it, then the password will be saved in the database.
  308.      */
  309.     promptPassword : function (aDialogTitle, aText, aPasswordRealm,
  310.                                aSavePassword, aPassword) {
  311.         this.log("===== promptPassword called() =====");
  312.  
  313.         if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
  314.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  315.  
  316.         var checkBox = { value : false };
  317.         var checkBoxLabel = null;
  318.         var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);
  319.  
  320.         // If hostname is null, we can't save this login.
  321.         if (hostname) {
  322.           var canRememberLogin = (aSavePassword ==
  323.                                   Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
  324.                                  this._pwmgr.getLoginSavingEnabled(hostname);
  325.   
  326.           // if checkBoxLabel is null, the checkbox won't be shown at all.
  327.           if (canRememberLogin)
  328.               checkBoxLabel = this._getLocalizedString("rememberPassword");
  329.   
  330.           if (!aPassword.value) {
  331.               // Look for existing logins.
  332.               var foundLogins = this._pwmgr.findLogins({}, hostname, null,
  333.                                                        realm);
  334.   
  335.               // XXX Like the original code, we can't deal with multiple
  336.               // account selection (bug 227632). We can deal with finding the
  337.               // account based on the supplied username - but in this case we'll
  338.               // just return the first match.
  339.               for (var i = 0; i < foundLogins.length; ++i) {
  340.                   if (foundLogins[i].username == username) {
  341.                       aPassword.value = foundLogins[i].password;
  342.                       // wallet returned straight away, so this mimics that code
  343.                       return true;
  344.                   }
  345.               }
  346.           }
  347.         }
  348.  
  349.         var ok = this._promptService.promptPassword(this._window, aDialogTitle,
  350.                                                     aText, aPassword,
  351.                                                     checkBoxLabel, checkBox);
  352.  
  353.         if (ok && checkBox.value && hostname) {
  354.             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  355.                            createInstance(Ci.nsILoginInfo);
  356.             newLogin.init(hostname, null, realm, username,
  357.                           aPassword.value, "", "");
  358.  
  359.             this.log("New login seen for " + realm);
  360.  
  361.             this._pwmgr.addLogin(newLogin);
  362.         }
  363.  
  364.         return ok;
  365.     },
  366.  
  367.     /* ---------- nsIAuthPrompt helpers ---------- */
  368.  
  369.  
  370.     /**
  371.      * Given aRealmString, such as "http://user@example.com/foo", returns an
  372.      * array of:
  373.      *   - the formatted hostname
  374.      *   - the realm (hostname + path)
  375.      *   - the username, if present
  376.      *
  377.      * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
  378.      * channels, e.g. "example.com:80 (httprealm)", null is returned for all
  379.      * arguments to let callers know the login can't be saved because we don't
  380.      * know whether it's http or https.
  381.      */
  382.     _getRealmInfo : function (aRealmString) {
  383.         var httpRealm = /^.+ \(.+\)$/;
  384.         if (httpRealm.test(aRealmString))
  385.             return [null, null, null];
  386.  
  387.         var uri = this._ioService.newURI(aRealmString, null, null);
  388.         var pathname = "";
  389.  
  390.         if (uri.path != "/")
  391.             pathname = uri.path;
  392.  
  393.         var formattedHostname = this._getFormattedHostname(uri);
  394.  
  395.         return [formattedHostname, formattedHostname + pathname, uri.username];
  396.     },
  397.  
  398.     /* ---------- nsIAuthPrompt2 prompts ---------- */
  399.  
  400.  
  401.  
  402.  
  403.     /*
  404.      * promptAuth
  405.      *
  406.      * Implementation of nsIAuthPrompt2.
  407.      *
  408.      * nsIChannel aChannel
  409.      * int        aLevel
  410.      * nsIAuthInformation aAuthInfo
  411.      */
  412.     promptAuth : function (aChannel, aLevel, aAuthInfo) {
  413.         var selectedLogin = null;
  414.         var checkbox = { value : false };
  415.         var checkboxLabel = null;
  416.         var epicfail = false;
  417.  
  418.         try {
  419.  
  420.             this.log("===== promptAuth called =====");
  421.  
  422.             // If the user submits a login but it fails, we need to remove the
  423.             // notification bar that was displayed. Conveniently, the user will
  424.             // be prompted for authentication again, which brings us here.
  425.             var notifyBox = this._getNotifyBox();
  426.             if (notifyBox)
  427.                 this._removeSaveLoginNotification(notifyBox);
  428.  
  429.             var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
  430.  
  431.  
  432.             // Looks for existing logins to prefill the prompt with.
  433.             var foundLogins = this._pwmgr.findLogins({},
  434.                                         hostname, null, httpRealm);
  435.             this.log("found " + foundLogins.length + " matching logins.");
  436.  
  437.             // XXX Can't select from multiple accounts yet. (bug 227632)
  438.             if (foundLogins.length > 0) {
  439.                 selectedLogin = foundLogins[0];
  440.                 this._SetAuthInfo(aAuthInfo, selectedLogin.username,
  441.                                              selectedLogin.password);
  442.                 checkbox.value = true;
  443.             }
  444.  
  445.             var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
  446.         
  447.             // if checkboxLabel is null, the checkbox won't be shown at all.
  448.             if (canRememberLogin && !notifyBox)
  449.                 checkboxLabel = this._getLocalizedString("rememberPassword");
  450.         } catch (e) {
  451.             // Ignore any errors and display the prompt anyway.
  452.             epicfail = true;
  453.             Components.utils.reportError("LoginManagerPrompter: " +
  454.                 "Epic fail in promptAuth: " + e + "\n");
  455.         }
  456.  
  457.         var ok = this._promptService.promptAuth(this._window, aChannel,
  458.                                 aLevel, aAuthInfo, checkboxLabel, checkbox);
  459.  
  460.         // If there's a notification box, use it to allow the user to
  461.         // determine if the login should be saved. If there isn't a
  462.         // notification box, only save the login if the user set the
  463.         // checkbox to do so.
  464.         var rememberLogin = notifyBox ? canRememberLogin : checkbox.value;
  465.         if (!ok || !rememberLogin || epicfail)
  466.             return ok;
  467.  
  468.         try {
  469.             var [username, password] = this._GetAuthInfo(aAuthInfo);
  470.  
  471.             var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
  472.                            createInstance(Ci.nsILoginInfo);
  473.             newLogin.init(hostname, null, httpRealm,
  474.                           username, password, "", "");
  475.  
  476.             // XXX We can't prompt with multiple logins yet (bug 227632), so
  477.             // the entered login might correspond to an existing login
  478.             // other than the one we originally selected.
  479.             selectedLogin = this._repickSelectedLogin(foundLogins, username);
  480.  
  481.             // If we didn't find an existing login, or if the username
  482.             // changed, save as a new login.
  483.             if (!selectedLogin) {
  484.                 // add as new
  485.                 this.log("New login seen for " + username +
  486.                          " @ " + hostname + " (" + httpRealm + ")");
  487.                 if (notifyBox)
  488.                     this._showSaveLoginNotification(notifyBox, newLogin);
  489.                 else
  490.                     this._pwmgr.addLogin(newLogin);
  491.  
  492.             } else if (password != selectedLogin.password) {
  493.  
  494.                 this.log("Updating password for " + username +
  495.                          " @ " + hostname + " (" + httpRealm + ")");
  496.                 // update password
  497.                 this._pwmgr.modifyLogin(selectedLogin, newLogin);
  498.  
  499.             } else {
  500.                 this.log("Login unchanged, no further action needed.");
  501.             }
  502.         } catch (e) {
  503.             Components.utils.reportError("LoginManagerPrompter: " +
  504.                 "Fail2 in promptAuth: " + e + "\n");
  505.         }
  506.  
  507.         return ok;
  508.     },
  509.  
  510.     asyncPromptAuth : function () {
  511.         return NS_ERROR_NOT_IMPLEMENTED;
  512.     },
  513.  
  514.  
  515.  
  516.  
  517.     /* ---------- nsILoginManagerPrompter prompts ---------- */
  518.  
  519.  
  520.  
  521.  
  522.     /*
  523.      * init
  524.      *
  525.      */
  526.     init : function (aWindow) {
  527.         this._window = aWindow;
  528.  
  529.         var prefBranch = Cc["@mozilla.org/preferences-service;1"].
  530.                          getService(Ci.nsIPrefService).getBranch("signon.");
  531.         this._debug = prefBranch.getBoolPref("debug");
  532.         this.log("===== initialized =====");
  533.     },
  534.  
  535.  
  536.     /*
  537.      * promptToSavePassword
  538.      *
  539.      */
  540.     promptToSavePassword : function (aLogin) {
  541.         var notifyBox = this._getNotifyBox();
  542.  
  543.         if (notifyBox)
  544.             this._showSaveLoginNotification(notifyBox, aLogin);
  545.         else
  546.             this._showSaveLoginDialog(aLogin);
  547.     },
  548.  
  549.  
  550.     /*
  551.      * _showLoginNotification
  552.      *
  553.      * Displays a notification bar.
  554.      *
  555.      */
  556.     _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
  557.         var oldBar = aNotifyBox.getNotificationWithValue(aName);
  558.         const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
  559.  
  560.         this.log("Adding new " + aName + " notification bar");
  561.         var newBar = aNotifyBox.appendNotification(
  562.                                 aText, aName,
  563.                                 "chrome://mozapps/skin/passwordmgr/key.png",
  564.                                 priority, aButtons);
  565.  
  566.         // The page we're going to hasn't loaded yet, so we want to persist
  567.         // across the first location change.
  568.         newBar.persistence++;
  569.  
  570.         // Sites like Gmail perform a funky redirect dance before you end up
  571.         // at the post-authentication page. I don't see a good way to
  572.         // heuristically determine when to ignore such location changes, so
  573.         // we'll try ignoring location changes based on a time interval.
  574.         newBar.timeout = Date.now() + 20000; // 20 seconds
  575.  
  576.         if (oldBar) {
  577.             this.log("(...and removing old " + aName + " notification bar)");
  578.             aNotifyBox.removeNotification(oldBar);
  579.         }
  580.     },
  581.  
  582.  
  583.     /*
  584.      * _showSaveLoginNotification
  585.      *
  586.      * Displays a notification bar (rather than a popup), to allow the user to
  587.      * save the specified login. This allows the user to see the results of
  588.      * their login, and only save a login which they know worked.
  589.      *
  590.      */
  591.     _showSaveLoginNotification : function (aNotifyBox, aLogin) {
  592.  
  593.         // Ugh. We can't use the strings from the popup window, because they
  594.         // have the access key marked in the string (eg "Mo&zilla"), along
  595.         // with some weird rules for handling access keys that do not occur
  596.         // in the string, for L10N. See commonDialog.js's setLabelForNode().
  597.         var neverButtonText =
  598.               this._getLocalizedString("notifyBarNeverForSiteButtonText");
  599.         var neverButtonAccessKey =
  600.               this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey");
  601.         var rememberButtonText =
  602.               this._getLocalizedString("notifyBarRememberButtonText");
  603.         var rememberButtonAccessKey =
  604.               this._getLocalizedString("notifyBarRememberButtonAccessKey");
  605.         var notNowButtonText =
  606.               this._getLocalizedString("notifyBarNotNowButtonText");
  607.         var notNowButtonAccessKey =
  608.               this._getLocalizedString("notifyBarNotNowButtonAccessKey");
  609.  
  610.         // XXX asac: anything more reasonable than "" we can guess for
  611.         // embedders that don't provide branding?)
  612.         var brandShortName =  "";
  613.         try {
  614.                 brandShortName =
  615.                       this._brandBundle.GetStringFromName("brandShortName");
  616.         } catch (e) {
  617.                 this.log ("no brandShortName available for prompting - most likely incomplete embedding.");
  618.         }
  619.         var notificationText  = this._getLocalizedString(
  620.                                         "savePasswordText", [brandShortName]);
  621.  
  622.         // The callbacks in |buttons| have a closure to access the variables
  623.         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
  624.         // without a getService() call.
  625.         var pwmgr = this._pwmgr;
  626.  
  627.  
  628.         var buttons = [
  629.             // "Remember" button
  630.             {
  631.                 label:     rememberButtonText,
  632.                 accessKey: rememberButtonAccessKey,
  633.                 popup:     null,
  634.                 callback: function(aNotificationBar, aButton) {
  635.                     pwmgr.addLogin(aLogin);
  636.                 }
  637.             },
  638.  
  639.             // "Never for this site" button
  640.             {
  641.                 label:     neverButtonText,
  642.                 accessKey: neverButtonAccessKey,
  643.                 popup:     null,
  644.                 callback: function(aNotificationBar, aButton) {
  645.                     pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
  646.                 }
  647.             },
  648.  
  649.             // "Not now" button
  650.             {
  651.                 label:     notNowButtonText,
  652.                 accessKey: notNowButtonAccessKey,
  653.                 popup:     null,
  654.                 callback:  function() { /* NOP */ } 
  655.             }
  656.         ];
  657.  
  658.         this._showLoginNotification(aNotifyBox, "password-save",
  659.              notificationText, buttons);
  660.     },
  661.  
  662.  
  663.     /*
  664.      * _removeSaveLoginNotification
  665.      *
  666.      */
  667.     _removeSaveLoginNotification : function (aNotifyBox) {
  668.  
  669.         var oldBar = aNotifyBox.getNotificationWithValue("password-save");
  670.  
  671.         if (oldBar) {
  672.             this.log("Removing save-password notification bar.");
  673.             aNotifyBox.removeNotification(oldBar);
  674.         }
  675.     },
  676.  
  677.  
  678.     /*
  679.      * _showSaveLoginDialog
  680.      *
  681.      * Called when we detect a new login in a form submission,
  682.      * asks the user what to do.
  683.      *
  684.      */
  685.     _showSaveLoginDialog : function (aLogin) {
  686.         const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
  687.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
  688.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
  689.             (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
  690.  
  691.         var brandShortName =
  692.                 this._brandBundle.GetStringFromName("brandShortName");
  693.  
  694.         var dialogText         = this._getLocalizedString(
  695.                                         "savePasswordText", [brandShortName]);
  696.         var dialogTitle        = this._getLocalizedString(
  697.                                         "savePasswordTitle");
  698.         var neverButtonText    = this._getLocalizedString(
  699.                                         "neverForSiteButtonText");
  700.         var rememberButtonText = this._getLocalizedString(
  701.                                         "rememberButtonText");
  702.         var notNowButtonText   = this._getLocalizedString(
  703.                                         "notNowButtonText");
  704.  
  705.         this.log("Prompting user to save/ignore login");
  706.         var userChoice = this._promptService.confirmEx(this._window,
  707.                                             dialogTitle, dialogText,
  708.                                             buttonFlags, rememberButtonText,
  709.                                             notNowButtonText, neverButtonText,
  710.                                             null, {});
  711.         //  Returns:
  712.         //   0 - Save the login
  713.         //   1 - Ignore the login this time
  714.         //   2 - Never save logins for this site
  715.         if (userChoice == 2) {
  716.             this.log("Disabling " + aLogin.hostname + " logins by request.");
  717.             this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
  718.         } else if (userChoice == 0) {
  719.             this.log("Saving login for " + aLogin.hostname);
  720.             this._pwmgr.addLogin(aLogin);
  721.         } else {
  722.             // userChoice == 1 --> just ignore the login.
  723.             this.log("Ignoring login.");
  724.         }
  725.     },
  726.  
  727.  
  728.     /*
  729.      * promptToChangePassword
  730.      *
  731.      * Called when we think we detect a password change for an existing
  732.      * login, when the form being submitted contains multiple password
  733.      * fields.
  734.      *
  735.      */
  736.     promptToChangePassword : function (aOldLogin, aNewLogin) {
  737.         var notifyBox = this._getNotifyBox();
  738.  
  739.         if (notifyBox)
  740.             this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin);
  741.         else
  742.             this._showChangeLoginDialog(aOldLogin, aNewLogin);
  743.     },
  744.  
  745.  
  746.     /*
  747.      * _showChangeLoginNotification
  748.      *
  749.      * Shows the Change Password notification bar.
  750.      *
  751.      */
  752.     _showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) {
  753.         var notificationText;
  754.         if (aOldLogin.username)
  755.             notificationText  = this._getLocalizedString(
  756.                                           "passwordChangeText",
  757.                                           [aOldLogin.username]);
  758.         else
  759.             notificationText  = this._getLocalizedString(
  760.                                           "passwordChangeTextNoUser");
  761.  
  762.         var changeButtonText =
  763.               this._getLocalizedString("notifyBarChangeButtonText");
  764.         var changeButtonAccessKey =
  765.               this._getLocalizedString("notifyBarChangeButtonAccessKey");
  766.         var dontChangeButtonText =
  767.               this._getLocalizedString("notifyBarDontChangeButtonText");
  768.         var dontChangeButtonAccessKey =
  769.               this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
  770.  
  771.         // The callbacks in |buttons| have a closure to access the variables
  772.         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
  773.         // without a getService() call.
  774.         var pwmgr = this._pwmgr;
  775.  
  776.         var buttons = [
  777.             // "Yes" button
  778.             {
  779.                 label:     changeButtonText,
  780.                 accessKey: changeButtonAccessKey,
  781.                 popup:     null,
  782.                 callback:  function(aNotificationBar, aButton) {
  783.                     pwmgr.modifyLogin(aOldLogin, aNewLogin);
  784.                 }
  785.             },
  786.  
  787.             // "No" button
  788.             {
  789.                 label:     dontChangeButtonText,
  790.                 accessKey: dontChangeButtonAccessKey,
  791.                 popup:     null,
  792.                 callback:  function(aNotificationBar, aButton) {
  793.                     // do nothing
  794.                 }
  795.             }
  796.         ];
  797.  
  798.         this._showLoginNotification(aNotifyBox, "password-change",
  799.              notificationText, buttons);
  800.     },
  801.  
  802.  
  803.     /*
  804.      * _showChangeLoginDialog
  805.      *
  806.      * Shows the Change Password dialog.
  807.      *
  808.      */
  809.     _showChangeLoginDialog : function (aOldLogin, aNewLogin) {
  810.         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  811.  
  812.         var dialogText;
  813.         if (aOldLogin.username)
  814.             dialogText  = this._getLocalizedString(
  815.                                     "passwordChangeText",
  816.                                     [aOldLogin.username]);
  817.         else
  818.             dialogText  = this._getLocalizedString(
  819.                                     "passwordChangeTextNoUser");
  820.  
  821.         var dialogTitle = this._getLocalizedString(
  822.                                     "passwordChangeTitle");
  823.  
  824.         // returns 0 for yes, 1 for no.
  825.         var ok = !this._promptService.confirmEx(this._window,
  826.                                 dialogTitle, dialogText, buttonFlags,
  827.                                 null, null, null,
  828.                                 null, {});
  829.         if (ok) {
  830.             this.log("Updating password for user " + aOldLogin.username);
  831.             this._pwmgr.modifyLogin(aOldLogin, aNewLogin);
  832.         }
  833.     },
  834.  
  835.  
  836.     /*
  837.      * promptToChangePasswordWithUsernames
  838.      *
  839.      * Called when we detect a password change in a form submission, but we
  840.      * don't know which existing login (username) it's for. Asks the user
  841.      * to select a username and confirm the password change.
  842.      *
  843.      * Note: The caller doesn't know the username for aNewLogin, so this
  844.      *       function fills in .username and .usernameField with the values
  845.      *       from the login selected by the user.
  846.      * 
  847.      * Note; XPCOM stupidity: |count| is just |logins.length|.
  848.      */
  849.     promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
  850.         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
  851.  
  852.         var usernames = logins.map(function (l) l.username);
  853.         var dialogText  = this._getLocalizedString("userSelectText");
  854.         var dialogTitle = this._getLocalizedString("passwordChangeTitle");
  855.         var selectedIndex = { value: null };
  856.  
  857.         // If user selects ok, outparam.value is set to the index
  858.         // of the selected username.
  859.         var ok = this._promptService.select(this._window,
  860.                                 dialogTitle, dialogText,
  861.                                 usernames.length, usernames,
  862.                                 selectedIndex);
  863.         if (ok) {
  864.             // Now that we know which login to change the password for,
  865.             // update the missing username info in the aNewLogin.
  866.  
  867.             var selectedLogin = logins[selectedIndex.value];
  868.  
  869.             this.log("Updating password for user " + selectedLogin.username);
  870.  
  871.             aNewLogin.username      = selectedLogin.username;
  872.             aNewLogin.usernameField = selectedLogin.usernameField;
  873.  
  874.             this._pwmgr.modifyLogin(selectedLogin, aNewLogin);
  875.         }
  876.     },
  877.  
  878.  
  879.  
  880.  
  881.     /* ---------- Internal Methods ---------- */
  882.  
  883.  
  884.  
  885.  
  886.     /*
  887.      * _getNotifyBox
  888.      *
  889.      * Returns the notification box to this prompter, or null if there isn't
  890.      * a notification box available.
  891.      */
  892.     _getNotifyBox : function () {
  893.         try {
  894.             // Get topmost window, in case we're in a frame.
  895.             var notifyWindow = this._window.top
  896.  
  897.             // Some sites pop up a temporary login window, when disappears
  898.             // upon submission of credentials. We want to put the notification
  899.             // bar in the opener window if this seems to be happening.
  900.             if (notifyWindow.opener) {
  901.                 var webnav = notifyWindow
  902.                                     .QueryInterface(Ci.nsIInterfaceRequestor)
  903.                                     .getInterface(Ci.nsIWebNavigation);
  904.                 var chromeWin = webnav
  905.                                     .QueryInterface(Ci.nsIDocShellTreeItem)
  906.                                     .rootTreeItem
  907.                                     .QueryInterface(Ci.nsIInterfaceRequestor)
  908.                                     .getInterface(Ci.nsIDOMWindow);
  909.                 var chromeDoc = chromeWin.document.documentElement;
  910.  
  911.                 // Check to see if the current window was opened with chrome
  912.                 // disabled, and if so use the opener window. But if the window
  913.                 // has been used to visit other pages (ie, has a history),
  914.                 // assume it'll stick around and *don't* use the opener.
  915.                 if (chromeDoc.getAttribute("chromehidden") &&
  916.                     webnav.sessionHistory.count == 1) {
  917.                     this.log("Using opener window for notification bar.");
  918.                     notifyWindow = notifyWindow.opener;
  919.                 }
  920.             }
  921.  
  922.  
  923.             // Find the <browser> which contains notifyWindow, by looking
  924.             // through all the open windows and all the <browsers> in each.
  925.             var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  926.                      getService(Ci.nsIWindowMediator);
  927.             var enumerator = wm.getEnumerator("navigator:browser");
  928.             var tabbrowser = null;
  929.             var foundBrowser = null;
  930.  
  931.             while (!foundBrowser && enumerator.hasMoreElements()) {
  932.                 var win = enumerator.getNext();
  933.                 tabbrowser = win.getBrowser(); 
  934.                 foundBrowser = tabbrowser.getBrowserForDocument(
  935.                                                   notifyWindow.document);
  936.             }
  937.  
  938.             // Return the notificationBox associated with the browser.
  939.             if (foundBrowser)
  940.                 return tabbrowser.getNotificationBox(foundBrowser)
  941.  
  942.         } catch (e) {
  943.             // If any errors happen, just assume no notification box.
  944.             this.log("No notification box available: " + e)
  945.         }
  946.  
  947.         return null;
  948.     },
  949.  
  950.  
  951.     /*
  952.      * _repickSelectedLogin
  953.      *
  954.      * The user might enter a login that isn't the one we prefilled, but
  955.      * is the same as some other existing login. So, pick a login with a
  956.      * matching username, or return null.
  957.      */
  958.     _repickSelectedLogin : function (foundLogins, username) {
  959.         for (var i = 0; i < foundLogins.length; i++)
  960.             if (foundLogins[i].username == username)
  961.                 return foundLogins[i];
  962.         return null;
  963.     },
  964.  
  965.     
  966.     /*
  967.      * _getLocalizedString
  968.      *
  969.      * Can be called as:
  970.      *   _getLocalizedString("key1");
  971.      *   _getLocalizedString("key2", ["arg1"]);
  972.      *   _getLocalizedString("key3", ["arg1", "arg2"]);
  973.      *   (etc)
  974.      *
  975.      * Returns the localized string for the specified key,
  976.      * formatted if required.
  977.      *
  978.      */ 
  979.     _getLocalizedString : function (key, formatArgs) {
  980.         if (formatArgs)
  981.             return this._strBundle.formatStringFromName(
  982.                                         key, formatArgs, formatArgs.length);
  983.         else
  984.             return this._strBundle.GetStringFromName(key);
  985.     },
  986.  
  987.  
  988.     /*
  989.      * _getFormattedHostname
  990.      *
  991.      * The aURI parameter may either be a string uri, or an nsIURI instance.
  992.      *
  993.      * Returns the hostname to use in a nsILoginInfo object (for example,
  994.      * "http://example.com").
  995.      */
  996.     _getFormattedHostname : function (aURI) {
  997.         var uri;
  998.         if (aURI instanceof Ci.nsIURI) {
  999.             uri = aURI;
  1000.         } else {
  1001.             uri = this._ioService.newURI(aURI, null, null);
  1002.         }
  1003.         var scheme = uri.scheme;
  1004.  
  1005.         var hostname = scheme + "://" + uri.host;
  1006.  
  1007.         // If the URI explicitly specified a port, only include it when
  1008.         // it's not the default. (We never want "http://foo.com:80")
  1009.         port = uri.port;
  1010.         if (port != -1) {
  1011.             var handler = this._ioService.getProtocolHandler(scheme);
  1012.             if (port != handler.defaultPort)
  1013.                 hostname += ":" + port;
  1014.         }
  1015.  
  1016.         return hostname;
  1017.     },
  1018.  
  1019.     /*
  1020.      * _getAuthTarget
  1021.      *
  1022.      * Returns the hostname and realm for which authentication is being
  1023.      * requested, in the format expected to be used with nsILoginInfo.
  1024.      */
  1025.     _getAuthTarget : function (aChannel, aAuthInfo) {
  1026.         var hostname, realm;
  1027.  
  1028.         // If our proxy is demanding authentication, don't use the
  1029.         // channel's actual destination.
  1030.         if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
  1031.             this.log("getAuthTarget is for proxy auth");
  1032.             if (!(aChannel instanceof Ci.nsIProxiedChannel))
  1033.                 throw "proxy auth needs nsIProxiedChannel";
  1034.  
  1035.             var info = aChannel.proxyInfo;
  1036.             if (!info)
  1037.                 throw "proxy auth needs nsIProxyInfo";
  1038.  
  1039.             // Proxies don't have a scheme, but we'll use "moz-proxy://"
  1040.             // so that it's more obvious what the login is for.
  1041.             var idnService = Cc["@mozilla.org/network/idn-service;1"].
  1042.                              getService(Ci.nsIIDNService);
  1043.             hostname = "moz-proxy://" +
  1044.                         idnService.convertUTF8toACE(info.host) +
  1045.                         ":" + info.port;
  1046.             realm = aAuthInfo.realm;
  1047.             if (!realm)
  1048.                 realm = hostname;
  1049.  
  1050.             return [hostname, realm];
  1051.         }
  1052.  
  1053.         hostname = this._getFormattedHostname(aChannel.URI);
  1054.  
  1055.         // If a HTTP WWW-Authenticate header specified a realm, that value
  1056.         // will be available here. If it wasn't set or wasn't HTTP, we'll use
  1057.         // the formatted hostname instead.
  1058.         realm = aAuthInfo.realm;
  1059.         if (!realm)
  1060.             realm = hostname;
  1061.  
  1062.         return [hostname, realm];
  1063.     },
  1064.  
  1065.  
  1066.     /**
  1067.      * Returns [username, password] as extracted from aAuthInfo (which
  1068.      * holds this info after having prompted the user).
  1069.      *
  1070.      * If the authentication was for a Windows domain, we'll prepend the
  1071.      * return username with the domain. (eg, "domain\user")
  1072.      */
  1073.     _GetAuthInfo : function (aAuthInfo) {
  1074.         var username, password;
  1075.  
  1076.         var flags = aAuthInfo.flags;
  1077.         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
  1078.             username = aAuthInfo.domain + "\\" + aAuthInfo.username;
  1079.         else
  1080.             username = aAuthInfo.username;
  1081.  
  1082.         password = aAuthInfo.password;
  1083.  
  1084.         return [username, password];
  1085.     },
  1086.  
  1087.  
  1088.     /**
  1089.      * Given a username (possibly in DOMAIN\user form) and password, parses the
  1090.      * domain out of the username if necessary and sets domain, username and
  1091.      * password on the auth information object.
  1092.      */
  1093.     _SetAuthInfo : function (aAuthInfo, username, password) {
  1094.         var flags = aAuthInfo.flags;
  1095.         if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
  1096.             // Domain is separated from username by a backslash
  1097.             var idx = username.indexOf("\\");
  1098.             if (idx == -1) {
  1099.                 aAuthInfo.username = username;
  1100.             } else {
  1101.                 aAuthInfo.domain   =  username.substring(0, idx);
  1102.                 aAuthInfo.username =  username.substring(idx+1);
  1103.             }
  1104.         } else {
  1105.             aAuthInfo.username = username;
  1106.         }
  1107.         aAuthInfo.password = password;
  1108.     }
  1109.  
  1110. }; // end of LoginManagerPrompter implementation
  1111.  
  1112.  
  1113. var component = [LoginManagerPromptFactory, LoginManagerPrompter];
  1114. function NSGetModule(compMgr, fileSpec) {
  1115.     return XPCOMUtils.generateModule(component);
  1116. }
  1117.